這份文章參考自
大陸網站:
http://computer.jges.mlc.edu.tw/index.php/zend-framework-2/76-di-dependency-injection
http://hi.baidu.com/thinkinginlamp/item/41c9bff024f03dcf531c26e6
外國人簡報
http://www.slideshare.net/fabpot/dependency-injection-with-php-and-php-53
<- 收穫很大
石頭大的文章
http://blog.roodo.com/rocksaying/archives/13454601.html
<- 最先爬到,但最慢看懂
===分隔線===
要了解DI,首先我們先假設一個情境
我們有一個class 叫作 User
那想當然我們使用的時候會產生一個實例 $user = new User() ;
但是這個user物件裡面牽涉很廣,可能有資料庫的連線,可能會有SESSION機制的存取
都可能會把總總要用到的物件,存進user這個類的特性。
class User
{
protected $storage ;
function __construct()
{
$this->storage = new SessionStorage() ;
}
function setLanguage($language)
{
$this->storage->set('language', language) ;
}
}
這樣會有一個問題,就是耦合性太高
想像如果你有一百支程式有用到SessionStorage這個類
而你新寫了一個SessionStorage2,那也許,你會必須改一百支程式。
另外一個說法是,太早耦合。
而相依性注入的強大優點就是低耦合,可以解決這樣的問題。
class User
{
protected $storage ;
function __construct($storage)
{
$this->storage = $storage ;
}
}
$storage = new SessionStorage() ;
$user = new User($storage) ;
我們來了解一下為甚麼一開始那一段程式還有甚麼問題?
class User
{
protected $storage ;
function __construct()
{
$this->storage = new SessionStorage('SESSION_ID') ; <- 要改了~
}
function setLanguage($language)
{
$this->storage->set('language', language) ;
}
}
也許SessionStorage需要改一個參數,喔不,你又要改一百支程式了。
恩...我們會怎麼解決呢?
define('SESSION_NAME', 'SESSION_ID') ;
$user = new User() ;
弄一個全域變數???
$user = new User('SESSION_ID') ;
恩,為了一個特性(物件)的一個特性....你覺得好嗎?
不好的地方是在SESSION_ID跟User不該有那麼直接的聯貫
再說,如果你想要改變SESSION的儲存的方式呢?可能放到檔案、可能放到MYSQL、可能放到記憶體
你會不會又,被迫寫下面的CODE
$user = new User('SESSION_ID', 'mysql') ;
<h3>所以這樣真的不太好!</h3>
所以孩子,放棄吧<del datetime="2013-11-14T06:50:43+00:00">去賣雞排吧</del>
<h3>以後就是這樣的生活了嗎?!</h3>
$storage = new MySQLSessionStorage('SESSION_ID') ;
//我假設MySQLSessionStorage繼承了SessionStorage,並override了一些方法
$user = new User($storage) ;
把你要的成員傳進去就對了,而這,就是相依性注入。就是那麼簡單。
<h3>讓我們更精進一點</h3>
class User
{
protected $storage ;
//你可以限制它來自哪種型別,放class應該也是合邏輯的
function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage ;
}
}
interface SessionStorageInterface
{
function get($key) ;
function set($key, $value) ;
}
class MySQLSessionStorage implements SessionStorageInterface
{
protected $data = array() ;
static function get($key)
{
return self::$data[$key];
}
static function set($key, $val)
{
self::$data[$key] = $value ;
}
}
讓我們回到原點一下,我們解決了甚麼問題?
我們使用相依性注入,減少對User class的耦合
於是我們可以針對storage進行改變而不用修改User的任何一行
而相依性注入不一定要使用__construct
我們來看看還有哪些方式?
$storage = new SessionStorage() ;
//constructor
$user = new User($storage) ;
//setter injection
$user = new User() ;
$user->setStorage($storage) ;
//property injection
$user = new User() ;
$user->storage = $storage ;
這~大概就是依賴注入的一些IDEA了。
但是,我們的確還是可以繼續往前延伸...
為了不影響User 類別,我們使用了依賴性注入
於是我們有了下面的程式碼
$storage = new SessionStorage('SESSION_ID') ;
$user = new User($storage) ;
//而且在user的constructor裡頭,你還是要$this-storage = $storage ;
但我們不希望我們每次要用到User的時候
就要上面寫一堆,除了SESSION,我們可以還要用到DB連線、LOG記錄等等的
( 然後在建構子裡面還要設定,如果很多的話,那不是產生一個User都要帶一堆參數? )
最好,還是一行可以搞定吧!!
$user = ( some magic can return user object )
為了達到這個目的,我們需要一個工具,我們叫它 - Container
Container在設計的時候要注意下面兩點
1." 裡面要被注入的類別,是不可以得知自己被container控制的狀況。
保持這樣的概念,可以確保屆時要在container進行置換的時候不會有問題。"
2." container可依照接收到的參數(是一個類),幫那個類的實例掛載property "
基本邏輯是這樣,Container是一個類,你告訴他你要產生User這個類的實例
然後他會幫你連User的特性都設定好(storage、dbh那些...)
最後把實例丟給變數$user接收。
class Container
{
protected $vlaues = array() ;
function __set($id, $value)
{
$this->values[$id] = $value ;
}
function __get($id)
{
if (is_callable($this->values[$id]))
{
return $this->values[$id]($this) ;
}
else
{
return $this->values[$id] ;
}
}
}
$container = new Container() ;
$contianer->session_name = 'SESSION_ID' ;
// $this->values['session_name'] = 'SESSION_ID' ;
$contianer->storage_class = 'SessionStorage' ;
// $this->values['storage_class'] = 'SessionStorage' ;
$container->user = function($c) //假設叫作fn1
{
return new User($c->storage) ;
} ;
// $this->values['user'] = closure fn ;
$container->storage = function($c) //假設叫作fn2
{
return new $c->storage_class($c->session_name) ;
}
// $this->values['storage'] = closure fn ;
$user = $container->user ;
//會觸發fn1,然後會去觸發參數$c->storage
//於是又觸發fn2,然後觸發$c->storage_class,去設定storage_class這個property
/*
這裡有點太精妙了建議看投影片從36頁開始看...
*/
不過這樣一來,我們就達到我們的目的了
不過這寫法實在太神了。
反而在設定的時候不是很好懂。所以,應該要寫一個版本
可能container類寫的複雜一點,但是設定的時候可以呆瓜一點。
總之,如果還想繼續了解的話,可以去看石頭大大那邊,也是不錯的實作。
同學,請用BBCODE,請參考樣式使用說明。
感謝兄台
我都是習慣在自己的地方寫一寫然後貼過來
有時候有點MISS,我會看你貼的東西